package org.example.nt;

import cn.gmssl.jce.provider.GMJCE;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.aliyun.gmsse.GMProvider;
import com.aliyun.gmsse.GMSSLContextSpi;
import com.aliyun.gmsse.GMX509KeyManagerFactory;
import com.aliyun.gmsse.GMX509TrustManagerFactory;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.KeyManagerFactorySpi;
import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

public class GmUtil {
    private static final Provider BC = new BouncyCastleProvider();

    public static final String ROOT_STORE_FILE = "sm-root.ks";
    public static final String SERVER_STORE_FILE = "sm-server.ks";
    public static final String CLIENT_STORE_FILE = "sm-client.ks";

    static {
        Security.addProvider(BC);
    }

    public static void main(String[] args) throws Exception {
        for (Provider.Service service : new GMJCE().getServices()) {
            System.out.println(service.getAlgorithm() + " " + service.getType());
            if (service.getType().equals("KeyManagerFactory")) {
                System.out.println(service.getAlgorithm() + " " + service.getType());
            }
//            if (service.getType().equals("TrustManagerFactory")) {
//                System.out.println(service.getAlgorithm() + " " + service.getType() + " " + service.getClassName());
//            }
//            if (service.getType().equals("Provider")) {
//                System.out.println(service.getAlgorithm() + " " + service.getType() + " " + service.getClassName());
//            }
        }


    }
    public static void gen()throws Exception{
        KeyStore rootStore = genRootStore();
        KeyStore serverStore = genServerStore();
        KeyStore clientStore = genClientStore();
        Certificate serverCert = serverStore.getCertificate("server");
        Certificate clientCert = clientStore.getCertificate("client");
        serverStore.setCertificateEntry("client", clientCert);
        clientStore.setCertificateEntry("server", serverCert);
    }

    private static KeyStore genServerStore() {
        try {
            KeyStore rootStore = loadStore(ROOT_STORE_FILE);
            KeyPair keyPair = genKey();
            PrivateKey caPrivateKey = (PrivateKey) rootStore.getKey("private", null);
            Certificate caCert = rootStore.getCertificate("cert");
            X509Certificate serverCert = genServerCert(keyPair, caPrivateKey, caCert);
            KeyStore keyStore = KeyStore.getInstance("BouncyCastle", BC);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("server", serverCert);
            keyStore.setKeyEntry("private", keyPair.getPrivate(), null, new Certificate[]{caCert, serverCert});
            keyStore.store(new FileOutputStream(FileUtil.file(SERVER_STORE_FILE)), null);
            return keyStore;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static KeyStore genClientStore() {
        try {
            KeyStore rootStore = loadStore(ROOT_STORE_FILE);
            KeyPair keyPair = genKey();
            PrivateKey caPrivateKey = (PrivateKey) rootStore.getKey("private", null);
            Certificate caCert = rootStore.getCertificate("cert");
            X509Certificate clientCert = genServerCert(keyPair, caPrivateKey, caCert);
            KeyStore keyStore = KeyStore.getInstance("BouncyCastle", BC);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("client", clientCert);
            keyStore.setKeyEntry("private", keyPair.getPrivate(), null, new Certificate[]{caCert, clientCert});
            keyStore.store(new FileOutputStream(FileUtil.file(CLIENT_STORE_FILE)), null);
            return keyStore;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static KeyStore genRootStore() {
        KeyPair keyPair = genKey();
        X509Certificate x509Certificate = genRootCert(keyPair);
        return store(ROOT_STORE_FILE, keyPair.getPrivate(), x509Certificate);
    }

    private static KeyStore loadStore(String filename) {
        try {
            KeyStore keyStore = KeyStore.getInstance("BouncyCastle", BC);
            keyStore.load(null, null);
            keyStore.load(new FileInputStream(FileUtil.file(filename)), null);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(keyStore, null);
            return keyStore;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static KeyStore store(String filename, PrivateKey privateKey, Certificate... certificates) {
        try {
            KeyStore keyStore = KeyStore.getInstance("BouncyCastle", BC);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("cert", certificates[0]);
            keyStore.setKeyEntry("private", privateKey, null, certificates);
            keyStore.store(new FileOutputStream(FileUtil.file(filename)), null);
            return keyStore;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void makeCertFile(X509Certificate x509Certificate, String filename) {

    }

    private static X509Certificate genClientCert(KeyPair keyPair, PrivateKey caPrivate, Certificate caCert) {
        try {
            X500Name x500Name = createStdBuilder(
                    "CN",
                    "comstar",
                    "Shanghai",
                    "Shanghai",
                    "client"
            ).build();
            X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
                    // 颁发者信息
                    new X509CertificateHolder(caCert.getEncoded()).getSubject(),
                    // 证书序列号
                    BigInteger.valueOf(3),
                    // 生效日期
                    new Date(System.currentTimeMillis() - 50 * 1000),
                    // 失效日期
                    new Date(System.currentTimeMillis() + 365 * 24 * 3600 * 1000),
                    // 使用者信息
                    x500Name,
                    // 证书公钥
                    keyPair.getPublic()
            )
                    // 证书扩展属性，根据需求设定，参数参考 《RFC 5280》
                    // 设置秘钥用法
                    .addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(keyPair.getPublic()))
                    // 基础约束，标识是否是CA证书，false标识为实体证书
                    .addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
            return signCertificate(certificateBuilder, caPrivate);
        } catch (Exception e) {
            throw new RuntimeException("生成证书异常", e);
        }
    }

    private static X509Certificate genServerCert(KeyPair keyPair, PrivateKey caPrivate, Certificate caCert) {
        try {
            X500Name x500Name = createStdBuilder(
                    "CN",
                    "comstar",
                    "Shanghai",
                    "Shanghai",
                    "server"
            ).build();
            X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
                    // 颁发者信息
                    new X509CertificateHolder(caCert.getEncoded()).getSubject(),
                    // 证书序列号
                    BigInteger.valueOf(2),
                    // 生效日期
                    new Date(System.currentTimeMillis() - 50 * 1000),
                    // 失效日期
                    new Date(System.currentTimeMillis() + 365 * 24 * 3600 * 1000),
                    // 使用者信息
                    x500Name,
                    // 证书公钥
                    keyPair.getPublic()
            )
                    // 证书扩展属性，根据需求设定，参数参考 《RFC 5280》
                    // 设置秘钥用法
                    .addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(keyPair.getPublic()))
                    // 基础约束，标识是否是CA证书，false标识为实体证书
                    .addExtension(Extension.basicConstraints, false, new BasicConstraints(false));
            return signCertificate(certificateBuilder, caPrivate);
        } catch (Exception e) {
            throw new RuntimeException("生成证书异常", e);
        }
    }

    private static X509Certificate signCertificate(X509v3CertificateBuilder builder, PrivateKey privateKey) {
        try {
            ContentSigner signer = new JcaContentSignerBuilder("SM3withSM2")
                    .setProvider(BC)
                    .build(privateKey);
            X509CertificateHolder holder = builder.build(signer);
            return new JcaX509CertificateConverter()
                    .setProvider(BC)
                    .getCertificate(holder);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static SubjectKeyIdentifier createSubjectKeyIdentifier(PublicKey publicKey) {
        return new SubjectKeyIdentifier(publicKey.getEncoded());
    }

    private static X509Certificate genRootCert(KeyPair keyPair) {
        // 产生秘钥对
        // 证书签名算法提供者
        try {
            X500Name x500Name = createStdBuilder(
                    "CN",
                    "comstar",
                    "Shanghai",
                    "Shanghai"
            ).build();
            X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
                    // 颁发者信息
                    x500Name,
                    // 证书序列号
                    BigInteger.valueOf(1),
                    // 生效日期
                    new Date(System.currentTimeMillis() - 50 * 1000),
                    // 失效日期
                    new Date(System.currentTimeMillis() + 365 * 24 * 3600 * 1000),
                    // 使用者信息，自签名证书和颁发者一致
                    x500Name,
                    // 证书公钥
                    keyPair.getPublic()
            )
                    // 证书扩展属性，根据需求设定，参数参考 《RFC 5280》
                    // 设置秘钥用法
                    .addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(keyPair.getPublic()))
                    .addExtension(Extension.keyUsage, false, new X509KeyUsage(
                            X509KeyUsage.keyCertSign
                                    | X509KeyUsage.digitalSignature
                                    | X509KeyUsage.keyEncipherment
                                    | X509KeyUsage.dataEncipherment
                                    | X509KeyUsage.cRLSign
                    ))
                    // 设置扩展秘钥用法：客户端身份认证、服务端身份认证
                    .addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage())
                    // 基础约束，标识是否是CA证书，false标识为实体证书
                    .addExtension(Extension.basicConstraints, true, new BasicConstraints(true))
                    // Netscape Cert Type SSL客户端身份认证
//                    .addExtension(MiscObjectIdentifiers.netscapeCertType, false, new NetscapeCertType(NetscapeCertType.sslClient))
                    ;
            // X.509格式证书对象生成
            return signCertificate(certificateBuilder, keyPair.getPrivate());
        } catch (Exception e) {
            throw new RuntimeException("生成证书异常", e);
        }
    }

    private static void verifyCertificate(X509Certificate certificate, X509Certificate[] trustedCertificates) {
        try {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            CertPath certPath = factory.generateCertPath(Arrays.asList(certificate));
            Set<TrustAnchor> trustAnchors = getTrustAnchors(trustedCertificates);
            PKIXParameters parameters = new PKIXParameters(trustAnchors);
            parameters.setRevocationEnabled(false);
            CertPathValidator validator = CertPathValidator.getInstance("PKIX");
            validator.validate(certPath, parameters);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static Set<TrustAnchor> getTrustAnchors(X509Certificate[] trustedCertificates) {
        Set<TrustAnchor> result = new HashSet<>();
        if (trustedCertificates == null) {
            for (X509Certificate trustedCertificate : trustedCertificates) {
                result.add(new TrustAnchor(trustedCertificate, null));
            }
        }
        return result;
    }

    private static X500NameBuilder createStdBuilder(String country, String organization, String province, String locality) {
        return createStdBuilder(country, organization, province, locality, null);
    }

    private static X500NameBuilder createStdBuilder(String country, String organization, String province, String locality, String organizationUnit) {
        X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
        // 国家代码
        builder.addRDN(BCStyle.C, country);
        // 组织
        builder.addRDN(BCStyle.O, organization);
        // 省份
        builder.addRDN(BCStyle.ST, province);
        // 地区
        builder.addRDN(BCStyle.L, locality);
        if (StrUtil.isNotBlank(organizationUnit)) {
            // 组织单位
            builder.addRDN(BCStyle.OU, organizationUnit);
        }
        return builder;
    }

    private static DERSequence extendedKeyUsage() {
        // 构造容器对象
        ASN1EncodableVector vector = new ASN1EncodableVector();
        // 客户端身份认证
        vector.add(KeyPurposeId.id_kp_clientAuth);
        // 服务端身份认证
        vector.add(KeyPurposeId.id_kp_serverAuth);

        vector.add(KeyPurposeId.anyExtendedKeyUsage);

        // 安全电子邮件
//        vector.add(KeyPurposeId.id_kp_emailProtection);
        return new DERSequence(vector);
    }

    private static KeyPair genKey() {
        return SecureUtil.generateKeyPair("SM2");
    }
}
